from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

import stackless
import sys
import logging
import weakref
import traceback
import time
import abc
import os
import types
import uuid
import struct
from os import path

import configobj

from kharlia.__core import *
from kharlia._core import *
from kharlia import core_pb2 as pb2

# Monkeypatch _socket
#sys.modules['_socket'] = _core._slsocket

# CONSTANTS
# Service states
SVC_STOPPED = 0
SVC_STARTED = 1
SVC_PAUSED = 2
SVC_ERROR = 3
SVC_STARTING = 4
SVC_STOPPING = 5

# App states
APP_STOPPED = 0
APP_STARTING = 1
APP_STARTED = 2
APP_STOPPING = 3

# GLOBALS

GLogger = None

def GetMainLogger():
    return GLogger

# NATIVE CLASS SUPPORT

def NativeClass(cls):
    '''
    Class decorator to specifying the native base class created with 
    Boost Python. The native base should be the first base class in the class
    definition.
    '''
    nativebase = cls.__bases__[0]
    assert cls.__base__ is nativebase, \
           'The native base must be the solid base (__base__)'
    assert isinstance(nativebase, GetBoostPythonClassType()), \
           'The native base must be an instance of Boost.Python.class'
    #assert cls.__name__ == nativebase.__name__, \
    #       'The native base must have the same name as the native class'
    cls.NativeBase = nativebase
    return cls

@NativeClass
class CObject(CObjectBase):
    '''
    This is the base class for object that implement advanced native
    functionality. The native code of this class allows native
    objects to retrieve a reference to self, allowing them to easily
    access python code from native code.
    
    This functionality is used to implement partial native functions,
    where native code is wrapped by python code.

    Multiple inheritence is not supported of more than one
    CObject derived class in Python or native code, exlcuding
    a Python's classes single native implementation class.
    '''
    def __init__(self):
        self.__initnative__()

    def __initnative__(self, cls=None, *args, **kwds):
        return CObject.InitNative(cls, self, *args, **kwds)

class CWeakRefDescr(object):
    '''
    Descriptor for hiding weak references.

    CWeakRefDescr(attrname) -> new weak ref descriptor that handles the actual
        weak ref object at self.attrname
    '''
    __slots__ = ['AttrName']

    def __init__(self, attrname):
        self.AttrName = attrname

    def __get__(self, inst, cls):
        if inst is None:
            return self
        wr = getattr(inst, self.AttrName)
        if wr:
            assert isinstance(wr, weakref.ReferenceType)
            return wr()
        else:
            return None

    def __set__(self, inst, value):
        if value is None:
            setattr(inst, self.AttrName, None)
        else:
            setattr(inst, self.AttrName, weakref.ref(value))

# CONFIG

class ConfigError(KharliaException):
    pass

class ConfigMergeError(ConfigError):
    pass

def _ConvertJSONUnicodeToStr(jdict):
    ndict = None
    if isinstance(jdict, dict):
        ndict = {}
        for key, value in jdict.iteritems():
            if isinstance(value, dict):
                ndict[str(key)] = converter(value)
            elif isinstance(value, list):
                ndict[str(key)] = converter(value)
            elif isinstance(value, unicode):
                ndict[str(key)] = str(value)
            else:
                ndict[str(key)] = value
    elif isinstance(jdict, list):
       ndict = []
       for value in jdict:
           if isinstance(value, unicode):
               ndict.append(str(value))
           else:
               ndict.append(value)
    return ndict

def LoadConfig(filename, merge=False, file_error=True, basefilename=None,
               _topcfg=True):
    '''
    Loads a ConfigObj file from disk using unrepr mode. If merge is true
    and a base file is specified in the config file, the file is
    merged with the base config file, but the values in the base config
    will not overwrite.
    '''
    if merge:
        cfg = configobj.ConfigObj(filename, unrepr=True, encoding='utf8',
                                  file_error=file_error)

        # If we've created a new file, add the base
        if len(cfg) == 0 and basefilename is not None:
            cfg['Base'] = basefilename

        # If a base file is specified, merge it
        if 'Base' in cfg:
            basefilename = cfg['Base']
            if not isinstance(basefilename, basestring):
                raise TypeError('base config filename must be a string')
                
            # Load the config file, it will merge its own base
            cfgbase = LoadConfig(basefilename, merge=True, file_error=file_error,
                                 _topcfg=False)
            
            # We have to use our own merge function because we want
            # behavior opposite to the default merge operation.
            def merge(indict, indictbase):
                for key, val in indictbase.iteritems():
                    haskey = key in indict
                    if (haskey and isinstance(indict[key], dict) and
                                   isinstance(val, dict)):
                        merge(indict[key], val)
                    elif not haskey:
                        indict[key] = val
            
            # Merge the configs
            merge(cfg, cfgbase)
    
    else:
        cfg = configobj.ConfigObj(filename, unrepr=True, encoding='utf8',
                                  file_error=file_error)

    # Correct indent type for newly created configs
    if cfg.indent_type == '':
        cfg.indent_type = '    '

    return cfg

# EVENT

class CEvent(object):
    '''
    Event dispatcher.
    
    Allows handlers to bind to the object and be called with arbitrary arguments
    and keywords whenever Dispatch() is called.
    
    More advanced handling such as attribute based or weak reference based are implemented
    in handler classes.
    
    Event handlers should define __nonzero__ which is used by the event
    to check whether a handler is still valid. If it is not valid, such as when a weak
    reference is invalid, the handler is not called and is removed from the handler list.
    '''
    __slots__ = ['_Handlers']
    def __init__(self):
        self._Handlers = []
        
    def Add(self, handler):
        '''
        Add a handler for this event. The handler must be callable and nonzero.
        '''
        if not callable(handler):
            raise TypeError('handler must be callable')
        if not handler:
            raise ValueError('bool(handler) must be True')
        self._Handlers.append(handler)
        
    def Remove(self, handler):
        '''
        Remove a handler for this event.
        '''
        self._Handlers.remove(handler)
        
    def RemoveSequence(self, handlers):
        for h in handlers:
            self._Handlers.remove(h)
        
    def Dispatch(self, sender, *args, **kwds):
        '''
        Dispatch the event by calling all handlers with all arguments.
        
        Invalid handlers are removed after all calls have been made.
        '''
        assert len(args) == 0, 'event dispatch requires keyword arguments only'
        removes = []
        
        for handler in self._Handlers:
            if handler:
                handler(sender=sender, **kwds)
            else:
                removes.append(handler)
        
        for handler in removes:
            self._Handlers.remove(handler)
            
    __call__ = Dispatch
    
    def AddAttr(self, handler, attrname):
        '''
        Add a handler by attribute name.
        
        Rather than calling handler directly, the specified attribute of handler
        is accessed during dispatch and called.
        '''
        wrapper = CAttrHandler(handler, attrname)
        assert wrapper
        self.Add(wrapper)       
    
    def AddWeak(self, handler):
        '''
        Add a handler by weak reference.
        
        This ensures that the event object does not keep the handler alive.
        '''
        wrapper = CWeakHandler(handler)
        assert wrapper
        self.Add(wrapper)
        
    def AddWeakAttr(self, handler, attrname):
        '''
        Add a handler by weak reference and attribute name.
        
        This combines the functionality of AddAttr() and AddWeak().
        '''
        wrapper = CWeakAttrHandler(handler, attrname)
        assert wrapper
        self.Add(wrapper)
        
    def RemoveWeakAttr(self, handler, attrname):
        '''
        Remove weak attribute handlers with the specified inner handler and attribute
        name.
        '''
        removes = []
        for h in self._Handlers:
            if (isinstance(h, CWeakAttrHandler) and
                    h._Target() == handler and
                    h._AttrName == attrname):
                removes.append(h)
        if len(removes) > 0:
            self.RemoveSequence(removes)
        else:
            raise ValueError('handler not found')

class CEventHandler(object):
    '''
    Base class for extended event handlers.
    '''
    __slots__ = []
    
    def __nonzero__(self):
        return False
    
    __hash__ = None

class CAttrHandler(CEventHandler):
    __slots__ = ['_Target', '_AttrName']
    
    def __init__(self, target, attrname):
        self._Target = target
        self._AttrName = attrname
        assert isinstance(attrname, basestring)
        
    def __nonzero__(self):
        return self._Target is not None
    
    def __call__(self, *args, **kwds):
        target = self._Target
        assert target is not None
        getattr(target, self._AttrName)(*args, **kwds)
        
    def __cmp__(self, other):
        if (self.__class__ == other.__class__ and
                self._Target == other._Target and
                self._AttrName == other._AttrName):
            return 0
        return -1
        
class CWeakHandler(CEventHandler):
    __slots__ = ['_Target']
    
    def __init__(self, target):
        self._Target = weakref.ref(target)
        assert hash(self._Target) != -1 # Make sure weakref has a hash
    
    def __call__(self, *args, **kwds):
        target = self._Target()
        if target is not None:
            target(*args, **kwds)
            
    def __nonzero__(self):
        return self._Target() is not None
    
    def __cmp__(self, other):
        t1 = self._Target()
        t2 = other._Target()
        
        if t1 is None or t2 is None:
            return -1
        if (self.__class__ == other.__class__ and t1 == t2):
            return 0
        return -1
    
class CWeakAttrHandler(CWeakHandler):
    __slots__ = ['_AttrName']
    
    def __init__(self, target, attrname):
        CWeakHandler.__init__(self, target)
        self._AttrName = attrname
        assert isinstance(attrname, basestring)
    
    def __call__(self, *args, **kwds):
        target = self._Target()
        if target is not None:
            getattr(target, self._AttrName)(*args, **kwds)
            
    def __cmp__(self, other):
        if CWeakHandler.__cmp__(self, other) != 0:
            return -1
        # If the super method returned zero, then classes and targets should match,
        # so just compare attribute names
        if self._AttrName == other._AttrName:
            return 0
        return -1

# SUBSYSTEM

@NativeClass
class CSubsystem(CSubsystemBase, CObject):
    def __init__(self, *args, **kwds):
        CObject.__init__(self, *args, **kwds)
        self.__initnative__()

    def Tick(self, deltaTime):
        raise NotImplementedError()

# TASKER

@NativeClass
class CTasker(CTaskerBase, CSubsystem):
    def __init__(self, *args, **kwds):
        self.__initnative__(None, *args, **kwds)
        CSubsystem.__init__(self)

# IO

@NativeClass
class CIOManager(CIOManagerBase, CSubsystem):
    def __init__(self, poolsize=3):
        self.__initnative__(None, poolsize)
        CSubsystem.__init__(self)

# SERVICE

class ServiceError(KharliaException):
    '''
    Base class for all service-related errors.
    '''

class ServiceTransitionError(ServiceError):
    '''
    Raised when an error occurs while a service is transitioning from one
    state to another.
    '''
    
class ServiceStateError(ServiceError):
    '''
    Attempted to perform an action on a service that was not appropriate for
    its current state.
    '''

class CService(object):
    '''
    CService(Manager, ConfigDict=None)
    
    The base class of all services. This is an abstract class defines the
    properties and functions that all services must share.
    '''
    __metaclass__ = abc.ABCMeta
    Name = None
    Depends = None
    Manual = False
    
    def __init__(self, manager, config=None):
        self.Manager = manager
        self.Config = config
        self._IsStarted = False
        self._IsStarting = False
        self.EventStarted = CEvent()
        self.EventStopped = CEvent()
    
    @property
    def IsStarted(self):
        return self._IsStarted

    @property
    def IsStarting(self):
        return self._IsStarting
    
    def Start(self):
        if self.IsStarted or self.IsStarting:
            return
        self._IsStarting = True
        GLogger.info('starting service: ' + self.Name)
        # Start all dependencies for this service
        for i in self.Depends:
            try:
                svc = self.Manager.GetServiceNoFail(i)
                if not svc.IsStarted:
                    svc.Start()
                    assert svc.IsStarted
            except ServiceError as e:
                raise ServiceTransitionError('Unable to Start service {0}, '
                                             'dependency not found: {1}'
                                             .format(self.Name, e.message))
        # Start implementation for subclasses
        self.OnStart()
        self._IsStarting = False
        self._IsStarted = True
        while not self.IsStarted:
            Yield()
        self.EventStarted(self)
        GLogger.debug('started service: ' + self.Name)

    def Stop(self):
        if not self.IsStarted:
            return
        GLogger.info('stopping service: ' + self.Name)
        # Stop any services that depend on this one
        for i in self.Manager.Services.itervalues():
            if self.Name in i.Depends:
                if i.IsStarted:
                    i.Stop()
        self.OnStop()
        self._IsStarted = False
        self.EventStopped(self)
        GLogger.debug('stopped service: ' + self.Name)

    def Restart(self):
        '''
        Calls a Start and Stop of the service.
        '''
        GLogger.info('restarting service: ' + self.Name)
        self.Stop()
        self.Start()
    
    @abc.abstractmethod
    def OnStart(self):
        pass

    @abc.abstractmethod
    def OnStop(self):
        pass
            
    def __repr__(self):
        return '<kharlia.core.CService \'{0}\'>'.format(self.Name)

class CTaskletService(CService):
    '''
    The base class for Tasklet-based services. Services that use tasklets in
    operation should subclass this class.
    '''
    __metaclass__ = abc.ABCMeta
    
    def __init__(self, manager, config=None):
        CService.__init__(self, manager, config)
        self.TargetPath = None
        self.Tasklet = None
        self.TaskletRunning = False
        self.KillingTasklet = False
        self.StopRequested = False
        self.RunOnStart = config.get('RunOnStart', False)
        
    def TaskletStartup(self):
        try:
            self.TaskletRunning = True
            self.TaskletMain()            
        finally:
            self.TaskletRunning = False
            if self.KillingTasklet:
                GLogger.debug('Tasklet for service {0} exiting on kill'
                              .format(self.Name))
            else:
                GLogger.debug('Tasklet for service {0} exiting on error'
                              .format(self.Name))
    
    def TaskletMain(self):
        if not self.TargetPath:
            raise ServiceError('no target path for service: ' + self.Name)
        target = FindGlobal(self.TargetPath)
        target(self, *self.Args)
    
    def Start(self):
        super(CTaskletService, self).Start()
        if self.RunOnStart:
            self.Tasklet.run()

    def OnStart(self):
        self.Tasklet = CreateTasklet(self.TaskletStartup)()
    
    def OnStop(self):
        self.KillingTasklet = True
        self.Tasklet.kill()
        self.KillingTasklet = False
        self.Tasklet = None

class CServiceManager(object):
    def __init__(self, app, config):
        self.App = app
        self.Services = {}
        self.Config = config
        if config is not None:
            self.LoadServices()

    def Cleanup(self):
        self.App = None
        self.Services = None
        self.Config = None

    def GetService(self, classname):
        return self.Services.get(classname, None)
    
    def GetServiceNoFail(self, classname):
        try:
            return self.Services[classname]
        except KeyError:
            raise ServiceError('service not found with name: {0}'.format(classname))
    
    def LoadServices(self):
        if self.Config is None:
            raise RuntimeError('no config specified')

        for k, v in self.Config.iteritems():
            # Skip this entry if disabled
            if not v.get('Enabled', False):
                continue
            GLogger.debug('loading service: ' + k)
            if k in self.Services:
                raise ServiceError('service already exists: ' + k)
                
            # Try resolving the name to get the class
            cls = None
            try:
                cls = FindGlobal(k)
            except ResolutionError as e:
                GLogger.error('Service class load failed for %s: %s', k, e)
                continue
            
            # Check for problems
            if cls is CService or cls is CTaskletService:
                raise TypeError('service class cannot be CService or CTaskletService')
            if not issubclass(cls, CService):
                raise TypeError('service class must be a subclass of CService')
            if cls.Name is None:
                raise ValueError('service name undefined')

            svc = cls(self, v)
            self.Services[k] = svc

    def StartServices(self, all=False):
        GLogger.debug('starting services')
        #start = time.clock()
        for svc in self.Services.itervalues():
            if not all and svc.Manual:
                continue
            if not svc.IsStarted:
                svc.Start()
                assert svc.IsStarted
        #elapsed = time.clock() - start
        #GLogger.debug('started services in %s seconds', elapsed)
    
    def StopServices(self, all=False):
        GLogger.debug('stopping services')
        start = time.clock()
        for svc in self.Services.itervalues():
            if not all and svc.Manual:
                continue
            if svc.IsStarted:
                svc.Stop()
        elapsed = time.clock() - start
        GLogger.debug('stopped services in %s seconds', elapsed)

# PACKAGE
# Note: This is an incomplete serialization system as an alternative to Python's
#       pickle objects.

class CPackage(object):
    Primitives = {bool, int, float, str}
    AllowedGlobalTypes = {types.FunctionType, types.BuiltinFunctionType}
    
    def __init__(self):
        self.GUID = None
        
class CPackageSave(CPackage):
    def __init__(self, outer, base):
        CPackage.__init__(self)
        self.Outer = outer
        self.Base = base
        self.Clear()
        
    def Clear(self):
        self.Out = None
        self.Bytes = None
        self.Strings = {}
        self.Objects = {}
        
    def Report(self):
        if self.Out:
            print('PackageSave Report:')
            print('Output size:', len(self.Bytes))
            print('Strings:')
            for k, v in self.Strings.iteritems():
                print(' ', k, '=', v)
            print('Objects:')
            for k, v in self.Objects.iteritems():
                print(' ', k, '=', v)
            
        
    def Save(self, file):
        '''
        Save the base object to file.
        '''
        if not self.GUID:
            self.GUID = uuid.uuid4()
        self.Out = pk = pb2.Package()
        # Create header
        pk.header.version = 1
        pk.header.guid = self.GUID.get_bytes()
        
        # Test object
        self.SaveObject(self.Base)
        self.Bytes = self.Out.SerializeToString()
        file.write(struct.pack('!I', len(self.Bytes)))
        file.write(self.Bytes)
        
    def SaveString(self, s):
        '''
        Save a string to the package.
        
        Returns a string id.
        '''
        if s not in self.Strings:
            index = len(self.Out.strings)
            self.Strings[s] = index
            self.Out.strings.append(s)
            return index
        else:
            return self.Strings[s]
        
    def SaveGlobal(self, obj):
        '''
        Save a global object to the package by a global name (module.name).
        
        Returns string ids for the module and name.
        '''
        if not (isinstance(obj, type) or obj in self.AllowedGlobalTypes):
            raise TypeError('not a global type')
        module = obj.__module__
        name = obj.__name__
        
        return self.SaveString(module), self.SaveString(name)
        
    def SaveGlobalInp(self, msg, obj):
        msg.module, msg.name = self.SaveGlobal(obj)
        
    def SaveObject(self, obj):
        '''
        Save a non-primitive object to the package.
        
        If the object has not yet been saved it will save and return a new
        reference ID for the object, otherwise the object's existing ID will be returned.
        
          0 = None
        > 0 = object reference
        < 0 = external persistent id
        '''
        if obj is None:
            return 0 # Null index
        if type(obj) in self.Primitives:
            raise TypeError('primitive')
        if obj not in self.Objects:
            if not ((hasattr(obj, '__pbsave__') and hasattr(obj, '__pbload__')) or
                    hasattr(obj, '__pbargs__')):
                raise AttributeError('object must defined __pbsave__ and __pbload__,'
                                     ' or __pbargs__ to be saved')
            print('PACK: Saving object:', obj)
            nid = len(self.Out.objects) + 1 # Zero is null index
            self.Objects[obj] = nid
            mo = self.Out.objects.add()
            try:
                # Get the object's constructor, or use its type
                if hasattr(obj, '__pbgetctor__'):
                    cons = obj.__pbgetctor__()
                else:
                    cons = type(obj)
                print(nid, 'constructor:', cons)
                
                # Get constructor args, if empty, serialize single null byte
                if hasattr(obj, '__pbargs__'):
                    margs = obj.__pbargs__(package=self, save=self.SaveObject)
                    if not margs:
                        sargs = '\0'
                        print(nid, 'saved empty ctor args')
                    else:
                        sargs = margs.SerializeToString()
                        print(nid, 'saved ctor args')
                else:
                    print(nid, 'no ctor args')
                                
                # Get the state
                if hasattr(obj, '__pbsave__'):
                    mstate = obj.__pbsave__(package=self, save=self.SaveObject)
                    sstate = mstate.SerializeToString()
                    print(nid, 'saved state')
                else:
                    print(nid, 'no state')
 
                # Save to the message
                #assert self.Out.objects[nid - 1] is mo
                mo.id = nid
                mo.type = 0
                self.SaveGlobalInp(mo.ctor, cons)
                mo.args = sargs
                mo.state = sstate
                assert mo.IsInitialized()
                print(nid, 'done')
                return nid
            
            except Exception:
                del self.Objects[obj]
                del self.Out.objects[nid - 1]
                raise
        else:
            return self.Objects[obj]
   
class CPackageLoad(CPackage):
    def __init__(self, outer):
        CPackage.__init__(self)
        self.Outer = outer
        self.Clear()
        
    def Clear(self):
        self.In = None
        self.Bytes = None
        self.Strings = {}
        self.Globals = {}
        self.Objects = {}
        self.Constructing = set()
        
    def Load(self, file):
        print('Loading package...')
        # Unpack the message size
        st = struct.Struct('!I')
        size = st.unpack(file.read(st.size))[0]
        # Read the message from the file
        self.Bytes = file.read(size)
        # Create the message
        self.In = pk = pb2.Package.FromString(self.Bytes)
        assert pk.header.version == 1
        assert pk.header.HasField('guid')
        guid = uuid.UUID(bytes=pk.header.guid)
        print('  Version:', pk.header.version)
        print('  GUID:', guid)
        
        # We assume that the first object is the root object
        if len(pk.objects) > 0:
            assert pk.objects[0].id == 1
            return self.LoadObject(pk.objects[0].id)
        else:
            return None
        
    def LoadString(self, id):
        # Is it necessary to cache them?
        if id not in self.Strings:
            s = self.In.strings[id]
            # Temporary unicode-to-string
            s = str(s)
            self.Strings[id] = s
            return s
        else:
            return self.Strings[id]
        
    def LoadGlobal(self, mid, nid):
        pair = mid, nid
        if pair in self.Globals:
            return self.Globals[pair]
        else:
            module, name = self.LoadString(mid), self.LoadString(nid)
            g = FindGlobal(module + '.' + name)
            if not (isinstance(g, type) or type(g) in self.AllowedGlobalTypes):
                raise ValueError('loaded global does not refer to a valid type')
            self.Globals[pair] = g
            return g
        
    def LoadObject(self, id):
        if id == 0:
            return None
        if id in self.Objects:
            return self.Objects[id]
        if id in self.Constructing:
            raise RuntimeError('constructor recursion in object load')
        # Load the object
        print('Loading object:', id)
        mo = self.In.objects[id - 1]
        assert mo.id == id
        assert mo.type == 0
        self.Constructing.add(id)
        try:
            if not mo.HasField('ctor'):
                raise RuntimeError('ctor not specified for object')
            ctor = self.LoadGlobal(mo.ctor.module, mo.ctor.name)
            if hasattr(ctor, '__pbctor__'):
                ctor = ctor.__pbctor__
            print(id, 'loaded ctor:', ctor)
            
            # Construct the object, the ctor should call both __new__ and __init__
            # NOTE: Need type checking
            if mo.HasField('args') and mo.args != '\0':
                obj = ctor(package=self, load=self.LoadObject, s=mo.args)
            else:
                obj = ctor()
            print(id, 'constructed object:', obj)
            
            # Object has now been constructed, and we can handle other references to it
            self.Constructing.remove(id)
            self.Objects[id] = obj
        
            # Have the object load its state
            if mo.HasField('state'):
                if not hasattr(obj, '__pbload__'):
                    raise TypeError('loaded object provides state, but constructed object'
                                    'does not have __pbload__')
                obj.__pbload__(package=self, load=self.LoadObject, s=mo.state)
                print(id, 'set state for:', obj)
                
            print(id, 'done')
            return obj    
        except Exception:
            self.Constructing.remove(id)
            if id in self.Objects:
                del self.Objects[id]
            raise
        
# APP

class CLogger(logging.Logger):
    def isEnabledFor(self, level):
        app = GetApp()
        if (app and self.name in app.SupressedLoggers and
                level <= app.SupressionLevel):
            return 0
        return super(CLogger, self).isEnabledFor(level)

class AppError(KharliaException):
    '''
    Base class for exceptions thrown by kharlia.core.CApp
    '''

@NativeClass
class CApp(CAppBase, CObject):
    '''
    Central application class, provides Tasklet, service, and subsystem managing,
    and the main loop.
    '''

    def __init__(self, appid, configdir='config', name=None):
        # Pre-init checks
        if not isinstance(appid, str):
            raise TypeError('appid must be instance of str')
        ct = stackless.getcurrent()
        if not ct.is_main:
            raise RuntimeError('App must be created on the main Tasklet')
        ct.block_trap = True
        # Initialization
        self.__initnative__()
        CObject.__init__(self)
        
        self.AppID = appid
        self.Args = None
        self.Config = None
        self.ConfigRoot = None
        self.ConfigDir = configdir
        self.Name = None
        self.State = APP_STOPPED
        self.SupressedLoggers = set()
        self.SupressionLevel = logging.INFO
        self.ExitMonitorTasklet = None
        self.EventExitRequested = CEvent()
        
        self.ServiceManager = None

    def IterSubsystems(self):
        subsystems = self.GetSubsystemList()
        for index, subsystem in subsystems:
            yield index, subsystem

    def RequestExit(self):
        if self.ExitRequested:
            return
        super(CApp, self).RequestExit()

        GLogger.debug('exiting run in %s seconds', self.ExitWaitTime)

        self.StartExitMonitor()     
        t = CreateTasklet(self.ServiceManager.StopServices)(all=True)
        t.critical = True
        self.EventExitRequested(self)

    def StartExitMonitor(self):
        taskers = []
        for index, subsystem in self.IterSubsystems():
            if isinstance(subsystem, CTasker):
                taskers.append(subsystem)
        
        if len(taskers) > 0:
            t = CreateTasklet(self.ExitMonitorMain)
            t.critical = True
            t(taskers, self.ForceExit)

    @property
    def CurrentTasklet(self):
        '''
        Alias for stackless.getcurrent(). May be extended in future.
        '''
        return stackless.getcurrent()
    
    def ExitMonitorMain(self, taskers, callback, interval=0.2):
        '''
        Exit monitor tasklet that allows tasklet managers to wind down before
        executing a callback. The purposes of this is to allow tasklets to execute
        in response to the program shutting down, without simply waiting X number
        of seconds before killing all tasklets. Current tasklet is excluded
        from the check.
        
        Arguments:
        taskers -- the list of taskers to check
        callback -- the object to call when all tasklets are dead
        interval -- the time to wait between checks (default 0.2)
        '''
        GLogger.debug('starting exit monitor')
        try:
            current = stackless.getcurrent()
            assert isinstance(current, CTaskletExt)
            sleep = Sleep
            done = False
            while self.IsRunning and not done:
                count = 0
                for tasker in taskers:
                    remaining = tasker.GetTaskletList(CTasker.ALL_GROUPS)
                    
                    # Don't count the current tasklet
                    if current in remaining:
                        count += len(remaining) - 1
                    else:
                        count += len(remaining)
                    
                if count > 0:
                    #GLogger.debug('tasklets remaining, sleeping for %s', interval)
                    sleep(interval)
                else:
                    GLogger.debug('tasklets are dead, exiting')
                    done = True
                    callback()
        finally:
            GLogger.debug('stopping exit monitor')

    def _Startup(self, args, trace, using_pydev):
        '''
        Steps:
            1. Initialize logging
            2. Load the configuration file
            3. Initialize the debug tracing support
            3. Create the default tasklet manager
            4. Create the Stackless IO manager
            5. Create a tasklet to call _FinishStartup which will:
                a. Create the service manager and start all non-manual services
                   if Core.App.AutoStartServices is True
                b. Iterate through Core.AutoExec and create a tasklet for each
                   function
            6. Call CAppBase.Run() to enter the main loop
        '''
        global GLogger
        
        self.State = APP_STARTING
        self.Args = args
        
        # Set up the logger
        assert GLogger is None
        logging.setLoggerClass(CLogger)

        rl = logging.getLogger()
        rl.setLevel(LOG_LEVEL)
        ch = logging.StreamHandler()
        ch.setLevel(LOG_LEVEL)
        # %(asctime)s,%(msecs)d:
        fmt = logging.Formatter('%(name)s: %(levelname)s: %(message)s', '%H:%M:%S')
        ch.setFormatter(fmt)
        rl.addHandler(ch)
        
        GLogger = logging.getLogger('core')
        assert type(GLogger) is CLogger

        # Begin initialization
        GLogger.debug('running app: %s', self.AppID)
        GLogger.info('running app: %s', self.AppID)
        GLogger.warning('running app: %s', self.AppID)
        GLogger.error('running app: %s', self.AppID)
        GLogger.critical('running app: %s', self.AppID)

        # Monkeypatch the configobj unrepr function, this forces it to
        # use the unicode_literals feature.
        import __future__
        ulcf = __future__.unicode_literals.compiler_flag
        def unrepr(s):
            if not s:
                return s
            return eval(compile(s, '<configobj-unrepr>', 'eval', ulcf), {})
        def xrepr(s):
            if type(s) is str:
                return 'b' + repr(s)
            if type(s) is unicode:
                return repr(s)[1:]
            return repr(s)
        configobj.unrepr = unrepr
        configobj.repr = xrepr

        # Load configuration file
        cfg_filename = path.join(self.ConfigDir, '{0}.ini'.format(self.AppID))
        basecfg_filename = path.join(self.ConfigDir, '{0}_default.ini'.format(self.AppID))
        croot = self.ConfigRoot = LoadConfig(cfg_filename,
                                             merge=True,
                                             file_error=False,
                                             basefilename=basecfg_filename)
        config = self.Config = croot['Core']['App']

        # Apply logging configuration
        levelmap = dict(
            NOTSET=logging.NOTSET,
            DEBUG=logging.DEBUG,
            INFO=logging.INFO,
            WARNING=logging.WARNING,
            ERROR=logging.ERROR,
            CRITICAL=logging.CRITICAL
            )
        self.SupressionLevel = levelmap[config['SupressionLevel']]
        for value in config['SupressedLoggers'].itervalues():
            if value:
                self.SupressedLoggers.add(value)
        logging.disable(levelmap[config['SupressAll']])
        
        # App config
        if self.Name is None:
            self.Name = config['Name']
        GLogger.info('app name: ' + self.Name)
        self.ThreadSleepTime = config.get('ThreadSleepTime', 1)
        self.TickRate = config.get('TickRate', 0)
        self.ExitWaitTime = config.get('ExitWaitTime', 1.0)

        #- Initialize the tasklet class and manager
        SetTraceFunc(trace)
        if using_pydev:
            import pydevd_tracing
            stimpl = pydevd_tracing.SetTrace
        else:
            stimpl = None

        CTaskletExt.Init(GLogger.error, GLogger.critical, stimpl)
        assert CTasker.GetDefault() is None
        tasker = CTasker()
        CTasker.SetDefault(tasker)
        ops = tasker.GetGroupOptions(0)
        ops.Timeout = config.get('TaskerTimeout', 5000)
        ops.SoftSwitch = config.get('TaskerSoftSwitch', True)
        ops.TotalTimeout = config.get('TaskerTotalTimeout', True)
        self.AddSubsystem(5, tasker)
        
        # Create the IO manager
        CIOManager.Init(CIOManager, poolsize=3)
        self.AddSubsystem(0, GetIOManager())

        # Perform remaining init in the run
        CreateTasklet(self._FinishStartup)()

        # Native run
        self.State = APP_STARTED

    def _FinishStartup(self):
        self.ServiceManager = CServiceManager(self, self.ConfigRoot['Core']['Services'])
        if self.Config.get('AutoStartServices', True):
            self.ServiceManager.StartServices()
            
        # Create tasklets for autoexec functions
        for name, value in self.ConfigRoot['Core']['AutoExec'].iteritems():
            if value is False:
                continue
            target = FindGlobal(name)
            assert callable(target)
            CreateTasklet(target)()

    def _Shutdown(self):
        global GLogger
        self.State = APP_STOPPING
        
        # Kill all tasklets
        tasker = CTasker.GetDefault()
        #assert self.Tasker is CTasker.GetDefault()
        tasker.KillTasklets(CTasker.ALL_GROUPS)

        self.ServiceManager.Cleanup()  # removes circular references
        self.ServiceManager = None

        self.RemoveSubsystem(tasker)
        CTasker.SetDefault(None)
        #self._Tasker = None
        #self._IOManager = None
        CIOManager.Exit()
        
        CTaskletExt.Exit()

        SetTraceFunc(None)
        ClearResolvedNameCache()

        # Save any changes to config
        self.ConfigRoot.write()

        # Final cleanup
        self.State = APP_STOPPED
        GLogger.info('app has exited')
        GLogger = None

    def Run(self, args=[], trace=None, using_pydev=False):
        '''
        Run the application.
        '''
        self._Startup(args, trace, using_pydev)

        super(CApp, self).Run()

        self._Shutdown()
        